#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <Eina.h>
#include <Ecore.h>
#include <Ecore_Con.h>
#include <Eet.h>

#include "Enm.h"
#include "enm_private.h"

EAPI int ENM_CLIENT_ADD = 0;
EAPI int ENM_CLIENT_DEL = 0;
EAPI int ENM_CLIENT_ERROR = 0;
EAPI int ENM_CLIENT_DATA = 0;

EAPI int ENM_SERVER_ADD = 0;
EAPI int ENM_SERVER_DEL = 0;
EAPI int ENM_SERVER_ERROR = 0;
EAPI int ENM_SERVER_DATA = 0;

static int _enm_init_count = 0;

Eina_Bool _enm_ecorecon_connected(void *data, int type, Ecore_Con_Event_Server_Add *ev);
Eina_Bool _enm_ecorecon_disconnected(void *data, int type, Ecore_Con_Event_Server_Del *ev);
Eina_Bool _enm_ecorecon_data(void *data, int type, Ecore_Con_Event_Server_Data *ev);

EAPI Eina_Bool
enm_init(void)
{
   if (_enm_init_count++ > 0) return EINA_TRUE;

   eina_init();
   eet_init();
   ecore_init();
   ecore_con_init();

   ENM_CLIENT_ADD = ecore_event_type_new();
   ENM_CLIENT_DEL = ecore_event_type_new();
   ENM_CLIENT_ERROR = ecore_event_type_new();
   ENM_CLIENT_DATA = ecore_event_type_new();

   ENM_SERVER_ADD = ecore_event_type_new();
   ENM_SERVER_DEL = ecore_event_type_new();
   ENM_SERVER_ERROR = ecore_event_type_new();
   ENM_SERVER_DATA = ecore_event_type_new();

   return EINA_TRUE;
}

EAPI Eina_Bool
enm_shutdown(void)
{
   if (--_enm_init_count != 0) return EINA_TRUE;

   ecore_con_shutdown();
   ecore_shutdown();
   eet_shutdown();
   eina_shutdown();

   return EINA_TRUE;
}

EAPI Enm_Server *enm_server_connect(const char *name, int port, const void *data)
{
   Enm_Server *new_enmserver = calloc(1, sizeof(Enm_Server));

   new_enmserver->data = data;
   new_enmserver->svr = ecore_con_server_connect(ECORE_CON_REMOTE_TCP | ECORE_CON_USE_MIXED, name, port, NULL);

   ecore_event_handler_add(ECORE_CON_EVENT_SERVER_ADD, (Ecore_Event_Handler_Cb)_enm_ecorecon_server_connected, new_enmserver);
   ecore_event_handler_add(ECORE_CON_EVENT_SERVER_DEL, (Ecore_Event_Handler_Cb)_enm_ecorecon_server_disconnected, new_enmserver);
   ecore_event_handler_add(ECORE_CON_EVENT_SERVER_DATA, (Ecore_Event_Handler_Cb)_enm_ecorecon_server_data, new_enmserver);

   return new_enmserver;
}

EAPI Enm_Server *enm_server_add(const char *name, int port, const void *data)
{
   Enm_Server *new_enmserver = calloc(1, sizeof(Enm_Server));

   new_enmserver->data = data;
   new_enmserver->svr = ecore_con_server_add(ECORE_CON_REMOTE_TCP | ECORE_CON_USE_MIXED, name, port, NULL);

   ecore_event_handler_add(ECORE_CON_EVENT_CLIENT_ADD, (Ecore_Event_Handler_Cb)_enm_ecorecon_client_connected, new_enmserver);
   ecore_event_handler_add(ECORE_CON_EVENT_CLIENT_DEL, (Ecore_Event_Handler_Cb)_enm_ecorecon_client_disconnected, new_enmserver);
   ecore_event_handler_add(ECORE_CON_EVENT_CLIENT_DATA, (Ecore_Event_Handler_Cb)_enm_ecorecon_client_data, new_enmserver);

   return new_enmserver;
}

Eina_Bool _enm_ecorecon_server_connected(void *data, int type, Ecore_Con_Event_Server_Add *ev)
{
   Enm_Server *enmserver = data;

   if( !enmserver ) return ECORE_CALLBACK_PASS_ON;

   /**
    * @todo Write a server structure in Enm.h
    *  so we use it here
    */
   ecore_event_add(ENM_SERVER_ADD, NULL, NULL, NULL);

   return ECORE_CALLBACK_PASS_ON;
}

Eina_Bool _enm_ecorecon_server_disconnected(void *data, int type, Ecore_Con_Event_Server_Del *ev)
{
   Enm_Server *enmserver = data;
   
   if( !enmserver ) return ECORE_CALLBACK_PASS_ON;
   
   ecore_event_add(ENM_SERVER_DEL, NULL, NULL, NULL);

   return ECORE_CALLBACK_PASS_ON;
}

Eina_Bool _enm_ecorecon_server_data(void *data, int type, Ecore_Con_Event_Server_Data *ev)
{
   /**
    * @todo send received data to EET
    */
   return ECORE_CALLBACK_PASS_ON;
}

Eina_Bool _enm_ecorecon_client_connected(void *data, int type, Ecore_Con_Event_Server_Add *ev)
{
   Enm_Server *enmserver = data;

   if( !enmserver ) return ECORE_CALLBACK_PASS_ON;

   /**
    * @todo Write a server structure in Enm.h
    *  so we use it here
    */
   ecore_event_add(ENM_CLIENT_ADD, NULL, NULL, NULL);

   return ECORE_CALLBACK_PASS_ON;
}

Eina_Bool _enm_ecorecon_server_disconnected(void *data, int type, Ecore_Con_Event_Server_Del *ev)
{
   Enm_Server *enmserver = data;
   
   if( !enmserver ) return ECORE_CALLBACK_PASS_ON;
   
   ecore_event_add(ENM_CLIENT_DEL, NULL, NULL, NULL);

   return ECORE_CALLBACK_PASS_ON;
}

Eina_Bool _enm_ecorecon_server_data(void *data, int type, Ecore_Con_Event_Server_Data *ev)
{
   /**
    * @todo send received data to EET
    */
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool _enm_eet_read_cb(const void *eet_data, size_t size, void *user_data)
{
   Enm_Server *enmserver = user_data;
   Enm_Message *enmmessage = eet_data;
   
   if( enmserver->us == EINA_TRUE )
     {
	ecore_event_add(ENM_CLIENT_DATA, enmmessage, NULL, NULL);
     }
   else
     {
	ecore_event_add(ENM_CLIENT_DATA, enmmessage, NULL, NULL);
     }
   
   
   return EINA_TRUE;
}

static Eina_Bool _enm_eet_write_cb(const void *data, size_t size, void *user_data)
{
   /**
    * To be defined, how do we know on which Ecore_Con_Server to send
    * if (ecore_con_server_send(conn, data, size) != (int) size)
    *return EINA_FALSE;
    */
   return EINA_TRUE;
}

Match_Type match_type[] = 
{
   { "Enm_Auth_Request"     , ENM_AUTH_REQUEST     , _enm_eet_enmauthrequest_dd     },
   { "Enm_Auth_Challenge"   , ENM_AUTH_CHALLENGE   , _enm_eet_enmauthchallenge_dd   },
   { "Enm_Auth_Answer"      , ENM_AUTH_ANSWER      , _enm_eet_enmauthanswer_dd      },
   { "Enm_Auth_Store"       , ENM_AUTH_STORE       , _enm_eet_enmauthstore_dd       },
   { "Enm_Node_Add"         , ENM_NODE_ADD         , _enm_eet_enmnodeadd_dd         },
   { "Enm_Node_Del"         , ENM_NODE_DEL         , _enm_eet_enmnodedel_dd         },
   { "Enm_Node_Value_Define", ENM_NODE_VALUE_DEFINE, _enm_eet_enmnodevaluedefine_dd },
   { "Enm_Node_Value_Set"   , ENM_NODE_VALUE_SET   , _enm_eet_enmnodevalueset_dd    },
   { "Enm_Node_Notify"      , ENM_NODE_NOTIFY      , _enm_eet_enmnodenotify_dd      },
   { "Enm_Link_Add"         , ENM_LINK_ADD         , _enm_eet_enmlinkadd_dd         },
   { "Enm_Link_Del"         , ENM_LINK_DEL         , _enm_eet_enmlinkdel_dd         },
   { "Enm_Link_Value_Define", ENM_LINK_VALUE_DEFINE, _enm_eet_enmlinkvaluedefine_dd },
   { "Enm_Link_Value_Set"   , ENM_LINK_VALUE_SET   , _enm_eet_enmlinkvalueset_dd    },
   { "Enm_Link_Notify"      , ENM_LINK_NOTIFY      , _enm_eet_enmlinknotify_dd      },
   { "Enm_Group_Add"        , ENM_GROUP_ADD        , _enm_eet_enmgroupadd_dd        },
   { "Enm_Group_Del"        , ENM_GROUP_DEL        , _enm_eet_enmgroupdel_dd        },
   { "Enm_Group_Associate"  , ENM_GROUP_ASSOCIATE  , _enm_eet_enmgroupassociate_dd  },
   { "Enm_Group_List"       , ENM_GROUP_LIST       , _enm_eet_enmgrouplist_dd       },
   { "Enm_Node_List"        , ENM_NODE_LIST        , _enm_eet_enmnodelist_dd        },
   { "Enm_Link_List"        , ENM_LINK_LIST        , _enm_eet_enmlinklist_dd        },
   { "Enm_Group_Watch"      , ENM_GROUP_WATCH      , _enm_eet_enmgroupwatch_dd      },
   { "Enm_Node_Watch"       , ENM_NODE_WATCH       , _enm_eet_enmnodewatch_dd       },
   { "Enm_Link_Watch"       , ENM_LINK_WATCH       , _enm_eet_enmlinkwatch_dd       },
   { "Enm_Group_Unwatch"    , ENM_LINK_UNWATCH     , _enm_eet_enmgroupunwatch_dd    },
   { "Enm_Node_Unwatch"     , ENM_NODE_UNWATCH     , _enm_eet_enmnodeunwatch_dd     },
   { "Enm_Link_Unwatch"     , ENM_LINK_UNWATCH     , _enm_eet_enmlinkunwatch_dd     },
   { NULL, 0, NULL }
};


Eina_Bool _enm_eet_init(void)
{
   Eet_Data_Descriptor_Class eddc;
   Eet_Data_Descriptor *edd;
   Eet_Data_Descriptor *un;
   int i;

   edd = unified = NULL;

   EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, Enm_Message);
   edd = eet_data_descriptor_stream_new(&eddc);

   eddc.version = EET_DATA_DESCRIPTOR_CLASS_VERSION;
   eddc.func.type_get = _enm_eet_type_get;
   eddc.func.type_set = _enm_eet_type_set;
   un = eet_data_descriptor_stream_new(&eddc);

   EET_DATA_DESCRIPTOR_ADD_BASIC(edd, Enm_Message, "version", version, EET_T_STRING);

   for (i = 0; match_type[i].name != NULL; i++)
     EET_DATA_DESCRIPTOR_ADD_MAPPING(un, match_type[i].name, match_type[i].edd());

   EET_DATA_DESCRIPTOR_ADD_UNION(edd, Enm_Message, "u", u, type, un);

   unified = edd;

   return EINA_TRUE;
}

static Eina_Bool _enm_eet_type_set(const char *type, void *data, Eina_Bool unknow)
{
   Enm_Message *ev = data;
   int i;

   if (unknow) return EINA_FALSE;

   for (i = 0; match_type[i].name != NULL; ++i)
     if (!strcmp(type, match_type[i].name))
       {
	  // Found
	  *ev = match_type[i].type;
	  return EINA_TRUE;
       }

   // Not found
   *ev = ENM_LAST;
   return EINA_FALSE;
}


static const char *_enm_eet_type_get(const void *data, Eina_Bool *unknow)
{
   const Clothing_Type *ev = data;
   int i;

   for (i = 0; match_type[i].name != NULL; ++i)
     if (*ev == match_type[i].type)
       {
	  // Found
	  return match_type[i].name;
       }


   if (unknow)
     *unknow = EINA_TRUE;
   
   // Not found
   return NULL;
}
